﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using burningmime.curves;

namespace ProjectJourney
{
    public class Painter : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
    {
        int brushWidth = 81;
        int brushHeight = 81;
        Color brushColor = Color.white;
        RectTransform rectTransform;
        List<Vector2> _points = new List<Vector2>();
        List<Vector2> _linearizedPoints = new List<Vector2>();
        List<CubicBezier> _curves = new List<CubicBezier>();
        int _deltaFrames;
        int _elapsedFrames;
        Texture2D _brushTex;
        RenderTexture _renderTexture;
        [SerializeField]
        float fitError = 40;
        [SerializeField]
        float lineDist = 10;
        [SerializeField]
        Button _clear;

        private void Start()
        {
            _renderTexture = new RenderTexture(Screen.width, Screen.height, 32);
            rectTransform = GetComponent<RectTransform>();
            var img = GetComponent<RawImage>();
            img.texture = _renderTexture;
            img.SetNativeSize();
            Clear(_renderTexture, Color.black);

            _brushTex = new Texture2D(brushWidth, brushHeight);
            int centerX = brushWidth / 2;
            int centerY = brushHeight / 2;
            for (int i = 0; i < brushWidth; i++)
            {
                for (int j = 0; j < brushHeight; j++)
                {
                    float r = Mathf.Sqrt((i - centerX) * (i - centerX) + (j - centerY) * (j - centerY));
                    brushColor.a = GetAlpha(r, centerX - 10, 0.1f);
                    _brushTex.SetPixel(i, j, brushColor);
                }
            }
            _brushTex.Apply();

            _deltaFrames = 3;
            _elapsedFrames = _deltaFrames;
            Time.fixedDeltaTime = 1f / 60f;
            GetComponent<RawImage>().texture = _renderTexture;
            _clear.onClick.AddListener(() =>
            {
                Clear(_renderTexture, Color.black);
            });
        }

        private void FixedUpdate()
        {
            if (_elapsedFrames >= _deltaFrames)
            {
                _elapsedFrames = 0;
                if (_points.Count > 0)
                {
                    FitCurves();
                    foreach (var curve in _curves)
                    {
                        DrawCurve(curve);
                    }
                    var last = _points[_points.Count - 1];
                    _points.Clear();
                    _points.Add(last);
                }
            }
            else
            {
                ++_elapsedFrames;
            }
        }

        float GetAlpha(float r, float radius, float attenuation)
        {
            if (r <= radius)
            {
                return 1f;
            }
            else
            {
                return Mathf.Max(0, 1f - (r - radius) * attenuation);
            }
        }

        void Clear(Texture tex, Color clearCol)
        {
            Color[] col = new Color[tex.width * tex.height];

            for (int i = 0; i < col.Length; i++)
            {
                col[i] = clearCol;
            }

            RenderTexture.active = _renderTexture;
            GL.Clear(true, true, Color.black);
            RenderTexture.active = null;
        }

        void Paint(Vector2 mousePosition)
        {
            var rect = rectTransform.rect;
            var pos = mousePosition - (Vector2)rectTransform.position;
            int x = Mathf.RoundToInt(Mathf.Clamp((pos.x - rect.x) / rect.width, 0f, 1f) * _renderTexture.width);
            int y = Mathf.RoundToInt(Mathf.Clamp((pos.y - rect.y) / rect.height, 0f, 1f) * _renderTexture.height);
            var tX = x - brushWidth / 2;
            var tY = y + brushHeight / 2;
            Graphics.DrawTexture(new Rect(tX, Screen.height - tY, brushWidth, brushHeight), _brushTex);
        }

        public void OnPointerDown(PointerEventData eventData)
        {
            _points.Clear();
            _points.Add(eventData.position);
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            _points.Clear();
        }

        public void OnDrag(PointerEventData eventData)
        {
            _points.Add(eventData.position);
        }


        void DrawCurve(CubicBezier curve)
        {
            float chord = (curve.p3 - curve.p0).magnitude;
            float contNet = (curve.p0 - curve.p1).magnitude + (curve.p2 - curve.p1).magnitude + (curve.p3 - curve.p2).magnitude;

            float length = (contNet + chord) / 2;
            int count = Mathf.RoundToInt(length / 5);

            RenderTexture.active = _renderTexture;
            GL.PushMatrix();
            GL.LoadPixelMatrix(0, Screen.width, Screen.height, 0);
            for (int i = 1; i <= count; i++)
            {
                float t = i / (float)count;
                Vector3 pixel = CubicBezier.CalculateSplinePoint(t, curve.p0, curve.p1, curve.p2, curve.p3);
                Paint(pixel);
            }
            GL.PopMatrix();
            RenderTexture.active = null;
        }

        private void FitCurves()
        {
            _linearizedPoints.Clear();
            _curves.Clear();
            CurvePreprocess.Linearize(_points, lineDist, _linearizedPoints);
            CurveFit.Fit(_linearizedPoints, fitError, _curves);
        }
    }
}
